package com.ktjy.order.service.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.ktjy.base.R;
import com.ktjy.goods.feign.FeignProductService;
import com.ktjy.order.mapper.OrderMapper;
import com.ktjy.order.properties.OrderProperties;
import com.ktjy.order.service.OrderService;
import com.ktjy.pojo.goods.Product;
import com.ktjy.pojo.order.Order;
import com.ktjy.service.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * 订单业务逻辑实现
 *
 * @author LILIBO
 * @since 2023-02-18
 */
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    /**
     * 服务发现客户端
     */
    @Resource
    private DiscoveryClient discoveryClient;

    /**
     * 服务调用客户端
     */
    @Resource
    private RestTemplate restTemplate;

    /**
     * 负载均衡客户端
     */
    @Resource
    private LoadBalancerClient loadBalancerClient;

    /**
     * 引入商品服务远程调用组件
     */
    @Resource
    private FeignProductService feignProductService;

    /**
     * 订单服务动态配置
     */
    @Resource
    private OrderProperties orderProperties;

    /**
     * 创建订单
     *
     * @param productId 商品id
     * @param uid 用户id
     * @return 订单对象
     */
    @Override
    @SentinelResource(value = "createOrder", blockHandler = "createOrderFallback") // Service层一般通过@SentinelResource注解的方式将方法标记为监控资源，可在控制台设置流控，blockHandler属性表示流控限制后的兜底回调方法，也可以使用fallback属性指定兜底回调方法，fallback属性可以处理业务异常，blockHandler属性只能处理流控和熔断异常时的兜底回调
    public Order createOrder(Long productId, Long uid) {
        // 1. 创建新的订单对象
        LocalDateTime now = LocalDateTime.now();
        String nowTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(now);
        String orderNo = orderProperties.getPrefix() + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(now);
        // 微服务远程调用，方式一：手动获取单个实例，拼接URL的方式远程获取商品信息
        // Product remoteProduct = getRemoteProductById(productId);
        // 微服务远程调用，方式二：负载均衡获取实例，拼接URL的方式远程获取商品信息
        // Product remoteProduct = getRemoteProductByIdWithLoadBalancer(productId);
        // 微服务远程调用，方式三：注解版本，负载均衡获取实例，拼接URL的方式远程获取商品信息
        // Product remoteProduct = getRemoteProductByIdWithLoadBalancerAnnotation(productId);

        // 微服务远程调用，方式四：声明式配置，使用Feign远程获取商品信息
        R result = feignProductService.getById(productId);
        Product remoteProduct = result != null ? JSON.parseObject(JSON.toJSONString(result.getData()), Product.class) : new Product();

        // 远程调用商品服务，获取商品价格
        double money = remoteProduct.getPrice();
        // 远程调用商品服务，获取商品名称
        String remark = remoteProduct.getName();
        Order order = new Order(orderNo, nowTime, money, remark, 0, 0, nowTime, nowTime);
        List<Product> products = new ArrayList<>();
        products.add(remoteProduct); // 订单中包含的商品信息
        order.setProducts(products);

        // 2. 保存订单到数据库
        boolean save = false;
        // [Sentinel] SphU编码方式进行流控和熔断功能
        try (Entry entry = SphU.entry("SphU_saveOrder")) { // 此处为资源名称，可在控制台设置流控
            save = super.save(order); // 保存订单到数据库
            log.info("订单[{}]保存{}！", orderNo, (save ? "成功" : "失败"));
        } catch (BlockException e) {
            log.error("[Sentinel] 服务器繁忙，订单[{}]保存{}！", orderNo, "失败");
            throw new RuntimeException(e);
        }
        return order;
    }

    /**
     * [Sentinel] 兜底回调-创建订单
     * blockHandler指定的回调方法，必须和原方法的参数、返回值完全一致，最后可以加上BlockException参数，用于接收流控和熔断异常
     * 也可以使用fallback属性指定兜底回调方法，fallback属性可以处理业务异常，最后的异常类型可以为Throwable，blockHandler属性只能处理流控和熔断异常
     */
    public Order createOrderFallback(Long productId, Long uid, BlockException e) {
        LocalDateTime now = LocalDateTime.now();
        String nowTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(now);
        Order order = new Order(); // 创建一个空的订单对象
        order.setId(0L);
        order.setOrderNo("");
        order.setOrderTime(nowTime);
        order.setOrderCost(0.0D);
        order.setRemark("[Sentinel] 服务器繁忙，请稍后再试~");
        order.setPayStatus(0);
        order.setStatus(0);
        order.setCreateTime(nowTime);
        order.setModifyTime(nowTime);
        order.setProducts(null);
        return order;
    }

    /**
     * 方式一：手动获取单个实例，拼接URL的方式远程获取商品信息
     *
     * @param productId 商品ID
     * @return 商品信息对象
     */
    private Product getRemoteProductById(Long productId) {
        // 通过服务发现客户端获取服务的第一个实例
        ServiceInstance serviceInstance = discoveryClient.getInstances("cloud-service-goods").get(0);

        // 构建商品服务的URL
        String url = serviceInstance.getUri() + "/product/" + productId;
        log.info(">> 发起访问的微服务地址为: {}", url);
        // 发起远程调用
        R result = restTemplate.getForObject(url, R.class);
        log.info("<< 远程获取到的数据结果为: {}", result);
        // 处理远程调用结果
        return result != null ? JSON.parseObject(JSON.toJSONString(result.getData()), Product.class) : new Product();
    }

    /**
     * 方式二：负载均衡获取实例，拼接URL的方式远程获取商品信息
     *
     * @param productId 商品ID
     * @return 商品信息对象
     */
    private Product getRemoteProductByIdWithLoadBalancer(Long productId) {
        // 按照负载均衡策略获取一个服务实例
        ServiceInstance serviceInstance = loadBalancerClient.choose("cloud-service-goods");

        // 构建商品服务的URL
        String url = serviceInstance.getUri() + "/product/" + productId;
        log.info(">> 发起访问的微服务地址为: {}", url);
        // 发起远程调用
        R result = restTemplate.getForObject(url, R.class);
        log.info("<< 远程获取到的数据结果为: {}", result);
        // 处理远程调用结果
        return result != null ? JSON.parseObject(JSON.toJSONString(result.getData()), Product.class) : new Product();
    }

    /**
     * 方式三：注解版本，负载均衡获取实例，拼接URL的方式远程获取商品信息
     *
     * @param productId 商品ID
     * @return 商品信息对象
     */
    private Product getRemoteProductByIdWithLoadBalancerAnnotation(Long productId) {
        // 构建商品服务的URL（在restTemplate的Bean定义处增加注解，使用微服务名 cloud-service-goods 即可自动实现负载均衡）
        String url = "http://cloud-service-goods/product/" + productId;
        log.info(">> 发起访问的微服务地址为: {}", url);
        // 发起远程调用
        R result = restTemplate.getForObject(url, R.class);
        log.info("<< 远程获取到的数据结果为: {}", result);
        // 处理远程调用结果
        return result != null ? JSON.parseObject(JSON.toJSONString(result.getData()), Product.class) : new Product();
    }

}
